﻿using System;
using System.Collections.Generic;
using System.Linq;
using SlamCms.Common;
using SlamCms.Collections;

namespace SlamCms.Data
{
    [Serializable]
	public class QueryParameters : ICloneable
	{
		public QueryParameters(ContentTypeCollection knownContentTypes)
		{
			KnownContentTypes = knownContentTypes;
			ContentTypes = new ContentTypeCollection();
			AdditionalFilters = new List<string>();
			ContentItemIds = new List<string>();
		    TaggedWithAll = new List<string>();
            NotTaggedWithAll = new List<string>();
			TaggedWithAny = new List<string>();
			Lists = new List<Guid>();
			Sites = new List<Guid>();
			Zones = new List<string>();
			FeaturedOnAllTags = new List<FeaturedOnTagFilter>();
			FeaturedOnAnyTags = new List<FeaturedOnTagFilter>();
			Cache = new QueryCaching() { Duration = 10 };
            Parameters = new ComparableDictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
		}

		public string Name { get; set; }
		public QueryCaching Cache { get; set; }
		public bool CountOnly { get; set; }
		public ContentTypeCollection KnownContentTypes { get; set; }

        public ComparableDictionary<string, object> Parameters { get; private set; }

	    // Filters
		public List<string> ContentItemIds { get; set; }
		public List<Guid> Lists { get; set; }
		public List<Guid> Sites { get; set; }
		public List<string> Zones { get; set; }
		public ContentTypeCollection ContentTypes { get; set; }
		public FilterStatus Status { get; set; }
		public List<string> TaggedWithAll { get; set; }
		public List<string> NotTaggedWithAll { get; set; }
		public List<string> TaggedWithAny { get; set; }
		public string RelatedToContentItemId { get; set; }
		public bool IncludeTags { get; set; }
		public int Take { get; set; }
		public List<FeaturedOnTagFilter> FeaturedOnAllTags { get; set; }
		public List<FeaturedOnTagFilter> FeaturedOnAnyTags { get; set; }
		public FeaturedOnLocationFilter FeaturedOnLocation { get; set; }
		public List<string> AdditionalFilters { get; set; }

		// Order by
		public string OrderByFeatureOnTagLevel { get; set; }
		public string OrderByFeatureOnLocationLevel { get; set; }
		public string OrderBy { get; set; }

		public void AddContentType(Type type)
		{
			ContentTypes.Add(type);
		}

		public void ClearContentTypes()
		{
			ContentTypes.Clear();
		}

        public object Clone()
        {
            var parameters = new QueryParameters(KnownContentTypes);

            parameters.ContentTypes = (ContentTypeCollection)ContentTypes.Clone();
            parameters.Name = Name;
            parameters.Cache = Cache.DeepClone();
            parameters.CountOnly = CountOnly;
            parameters.ContentItemIds = ContentItemIds.ToList();
            parameters.Lists = Lists.ToList();
            parameters.Sites = Sites.ToList();
            parameters.Zones = Zones.ToList();
            parameters.Status = Status;
            parameters.TaggedWithAll = TaggedWithAll.ToList();
            parameters.NotTaggedWithAll = NotTaggedWithAll.ToList();
            parameters.TaggedWithAny = TaggedWithAny.ToList();
            parameters.RelatedToContentItemId = RelatedToContentItemId;
            parameters.IncludeTags = IncludeTags;
            parameters.Take = Take;
            parameters.FeaturedOnAllTags = FeaturedOnAllTags.Select(x => x.DeepClone()).ToList();
            parameters.FeaturedOnAnyTags = FeaturedOnAnyTags.Select(x => x.DeepClone()).ToList();
            parameters.FeaturedOnLocation = FeaturedOnLocation == null ? null : FeaturedOnLocation.DeepClone();
            parameters.AdditionalFilters = AdditionalFilters.ToList();
            parameters.OrderByFeatureOnTagLevel = OrderByFeatureOnTagLevel;
            parameters.OrderByFeatureOnLocationLevel = OrderByFeatureOnLocationLevel;
            parameters.OrderBy = OrderBy;

            return parameters;
        }

	    public override int GetHashCode()
		{
			unchecked // Overflow is fine, just wrap
			{
				var hash = 17;

                hash = hash * 23 + Status.GetHashCode();
                hash = hash * 23 + IncludeTags.GetHashCode();
                hash = hash * 23 + Take.GetHashCode();
                
                if (Name != null)
                    hash = hash * 23 + Name.GetHashCode();

				if (ContentItemIds != null)
					hash = ContentItemIds.Aggregate(hash, (current, item) => current*23 + item.GetHashCode());
				if (Lists != null)
					hash = Lists.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (Sites != null)
					hash = Sites.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (Zones != null)
					hash = Zones.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (ContentTypes != null)
					hash = ContentTypes.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				
				if (TaggedWithAll != null)
					hash = TaggedWithAll.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
                if (NotTaggedWithAll != null)
                    hash = NotTaggedWithAll.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (TaggedWithAny != null)
					hash = TaggedWithAny.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (RelatedToContentItemId != null)			
					hash = hash * 23 + RelatedToContentItemId.GetHashCode();

				if (FeaturedOnAllTags != null)
					hash = FeaturedOnAllTags.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (FeaturedOnAnyTags != null)
					hash = FeaturedOnAnyTags.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (FeaturedOnLocation != null)				
					hash = hash*23 + FeaturedOnLocation.GetHashCode();
				if (AdditionalFilters != null)
					hash = AdditionalFilters.Aggregate(hash, (current, item) => current * 23 + item.GetHashCode());
				if (OrderByFeatureOnTagLevel != null)
					hash = hash*23 + OrderByFeatureOnTagLevel.GetHashCode();
				if (OrderByFeatureOnLocationLevel != null)
					hash = hash*23 + OrderByFeatureOnLocationLevel.GetHashCode();
				if (OrderBy != null)
					hash = hash*23 + OrderBy.GetHashCode();

			    hash = hash*23 + Parameters.GetHashCode();

				return hash;
			}
		}

        [Serializable]
		public class FeaturedOnTagFilter : ICloneable
		{
			public static FeaturedOnTagFilter Create(string tagIdentifier, FilterOperator filterOperator, int featureLevel)
			{
				return new FeaturedOnTagFilter()
				{
					TagIdentifier = tagIdentifier,
					FilterOperator = filterOperator,
					FeatureLevel = featureLevel
				};
			}

			public string TagIdentifier { get; set; }
			public FilterOperator FilterOperator { get; set; }
			public int FeatureLevel { get; set; }

            public object Clone()
            {
                return this.DeepClone();
            }

            public override int GetHashCode()
			{
				unchecked
				{
					var hash = 17;

					hash = hash * 23 + TagIdentifier.GetHashCode();
					hash = hash * 23 + FilterOperator.GetHashCode();
					hash = hash * 23 + FeatureLevel.GetHashCode();

					return hash;
				}
			}
		}

        [Serializable]
		public class FeaturedOnLocationFilter : ICloneable
		{
			public static FeaturedOnLocationFilter Create(string location, FilterOperator filterOperator, int featureLevel)
			{
				return new FeaturedOnLocationFilter()
				{
					Location = location,
					FilterOperator = filterOperator,
					FeatureLevel = featureLevel
				};
			}

			public string Location { get; set; }
			public FilterOperator FilterOperator { get; set; }
			public int FeatureLevel { get; set; }

            public object Clone()
            {
                return this.DeepClone();
            }

			public override int GetHashCode()
			{
				unchecked
				{
					var hash = 17;

					hash = hash * 23 + Location.GetHashCode();
					hash = hash * 23 + FilterOperator.GetHashCode();
					hash = hash * 23 + FeatureLevel.GetHashCode();

					return hash;
				}
			}
		}

        [Serializable]
		public class PageFilter : ICloneable
		{
			public static PageFilter Create(int pageNumber, int pageSize)
			{
				return new PageFilter()
				{
					Number = pageNumber,
					PageSize = pageSize
				};
			}

            public object Clone()
            {
                return this.DeepClone();
            }

			public int Number { get; set; }
			public int PageSize { get; set; }
		}

        [Serializable]
		public class QueryCaching : ICloneable
		{
			public QueryCacheBehavior Behavior { get; set; }
			public int Duration { get; set; }

            public object Clone()
            {
                return this.DeepClone();
            }
		}
    }
}